﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;         // TODO: Should I localize? Not goal for this release?
using System.ComponentModel;
using System.Diagnostics;
using System.Threading;             // TODO: Is there anything I should do to be thread safe?
using System.Configuration.Install;
using Microsoft.Win32;

using Microsoft.Exchange.Data.Transport;
using Microsoft.Exchange.Data.Transport.Smtp;
using Microsoft.Exchange.Data.Transport.Routing;

namespace RoutingRuleAgent
{
    /// <summary>
    /// Run when installing the assembly, this function sets up the required 
    /// application event logging registry keys
    /// </summary>
    [RunInstaller(true)]
    public class RoutingRuleEventLogInstaller : Installer
    {
        private EventLogInstaller routingeventLogInstaller;

        public RoutingRuleEventLogInstaller()
        {
            // Create an instance of an EventLogInstaller.
            routingeventLogInstaller = new EventLogInstaller();
            // Set the source name of the event log.
            routingeventLogInstaller.Source = "RoutingRuleAgent";
            // Set the event log that the source writes entries to.
            routingeventLogInstaller.Log = "Application";
            // Add routingeventLogInstaller to the Installer collection.
            Installers.Add(routingeventLogInstaller);

            RegistryKey newRegKey = Registry.LocalMachine.CreateSubKey("System\\CurrentControlSet\\Services\\MSExchangeRoutingRuleAgent\\Diagnostics");
            newRegKey.SetValue("General", 0);
        }
    }


    /// <summary>
    /// This is the factory for RoutingRuleAgent
    /// </summary>
    public class RoutingRuleFactory : RoutingAgentFactory
    {
        /// <summary>
        /// Creates a new instance of <see cref="RoutingRuleAgent"/>
        /// </summary>
        /// <param name="server">Smtp Server representation</param>
        /// <returns>New instance of <see cref="RoutingRuleAgent"/></returns>
        public override RoutingAgent CreateAgent(SmtpServer server)
        {
            return new RoutingRuleAgent();
        }
    }

    /// <summary>
    /// RoutingRuleAgent: Checks header and reroutes accordingly
    /// </summary>
    public class RoutingRuleAgent : RoutingAgent
    {

        private int eventLogLevel = -1; // Don't log if the proper keys aren't setup because this could cause problems later.
        private RegistryKey eventLogRegKey; // This is a member in case we want to add registry monitoring

        /// <summary>
        /// RoutingRuleAgent Constructor: executes for each loaded instance
        /// </summary>
        public RoutingRuleAgent()
        {
            // Read EventLogLevel from Registry (this gets called with each new instance, a little unnecessary)
            this.eventLogRegKey = Registry.LocalMachine.OpenSubKey("System\\CurrentControlSet\\Services\\MSExchangeRoutingRuleAgent\\Diagnostics");

            if (this.eventLogRegKey != null)
            {
                if (eventLogRegKey.GetValue("General") != null)
                {
                    this.eventLogLevel = ((int)eventLogRegKey.GetValue("General"));

                    EventLog.WriteEntry("RoutingRuleAgent", "Event logging set to level " + this.eventLogLevel.ToString() + ".");
                }
                else
                {
                    // this could be used at a later date for adding necessary logging entries or alternate logging.  For now we'll use the .NET installer defined above (installutil.exe).
                }
            }
            else
            {
                // only create stub -- we still aren't sure the install was sucessful enough to log events without crashing
                // Registry.LocalMachine.CreateSubKey("System\\CurrentControlSet\\Services\\MSExchangeRoutingRuleAgent\\Diagnostics");
                // will networkservice have the necessary privs? NO
            }

            // Event handlers
            this.OnResolvedMessage += new ResolvedMessageEventHandler(RoutingRuleAgent_OnResolvedMessage);
            this.OnRoutedMessage += new RoutedMessageEventHandler(RoutingRuleAgent_OnRoutedMessage);
        }

        /// <summary>
        /// This is the OnResolvedMessage code that reroutes the recipients
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        void RoutingRuleAgent_OnResolvedMessage(ResolvedMessageEventSource source, QueuedMessageEventArgs e)
        {
            try
            {
                if (eventLogLevel >= 5)
                    EventLog.WriteEntry("RoutingRuleAgent", "Entering OnResolved for message " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 1);

                // look to see if there is an X-RoutingRuleDomain header
                Microsoft.Exchange.Data.Mime.Header domainheader;
                domainheader = e.MailItem.Message.MimeDocument.RootPart.Headers.FindFirst("X-MS-Exchange-Organization-RoutingRuleDomain");

                if (domainheader != null)
                {
                    // if header exists, check to see if it is valid
                    Microsoft.Exchange.Data.Transport.RoutingDomain overridedomain;
                    overridedomain = new RoutingDomain((domainheader.Value.ToString()));

                    if (overridedomain.IsValid())
                    {
                        // count modified recipients so we can log proper event
                        int nCount = 0;

                        // if the domain is valid, then reroute each recipient in the recipient collection
                        foreach (EnvelopeRecipient recipient in e.MailItem.Recipients)
                        {
                   /*         if (recipient.RoutingOverride.Domain != null)
                            {
                                if (eventLogLevel >= 0)
                                    EventLog.WriteEntry("RoutingRuleAgent", "Recipient " + recipient.Address.ToString() + " on " + e.MailItem.Message.MessageId.ToString() + " override already set : " + recipient.RoutingOverride.Domain.ToString() + ".", EventLogEntryType.Warning, 7);
                            }
                            else
                            { */
                                if (eventLogLevel >= 3)
                                    EventLog.WriteEntry("RoutingRuleAgent", "Overriding recipient " + recipient.Address.ToString() + " on " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 4);

                                recipient.SetRoutingOverride(overridedomain);
                                nCount++;
                            //}
                        }

                        if (nCount > 0)
                        {
                            if (eventLogLevel >= 1)
                                EventLog.WriteEntry("RoutingRuleAgent", nCount.ToString() + " recipients were overridden on " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 5);
                        }

                        // check X-RoutingRuleCompleted and if it doesn't exist, add it so we don't loop
                        Microsoft.Exchange.Data.Mime.Header overrideheader;
                        overrideheader = e.MailItem.Message.MimeDocument.RootPart.Headers.FindFirst("X-MS-Exchange-Organization-RoutingRuleCompleted");

                        if (overrideheader == null)
                        {
                            if (eventLogLevel >= 5)
                                EventLog.WriteEntry("RoutingRuleAgent", "Setting x-ms-exchange-organization-RoutingRuleCompleted on " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 6);

                            Microsoft.Exchange.Data.Mime.TextHeader nhNewHeader = new Microsoft.Exchange.Data.Mime.TextHeader("X-MS-Exchange-Organization-RoutingRuleCompleted", "true");
                            e.MailItem.Message.RootPart.Headers.InsertAfter(nhNewHeader, domainheader);


                        }
                    }
                }

                if (eventLogLevel >= 5)
                    EventLog.WriteEntry("RoutingRuleAgent", "Exiting OnResolved for message " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 8);

            }
            catch (Exception ex)
            {
                if (eventLogLevel >= 3)
                    EventLog.WriteEntry("RoutingRuleAgent", "Exception (OnResolved): " + ex.Message + "\n" + ex.StackTrace, EventLogEntryType.Information, 10);

                // TODO: if OnResolved is failing, perhaps we should skip OnRouted to prevent loops
            }
            finally
            {
                // TODO: Decide what to do with the message if we fail.  Since this could be used in compliance scenarios, perhaps we should poison it or suspend it or something?
            }
        }

        /// <summary>
        /// Because Transport Rules Agent fires after OnResolvedMessage as an OnRoutedMessageAgent, we need to resubmit the message through the pipeline
        /// </summary>
        /// <param name="source"></param>
        /// <param name="e"></param>
        void RoutingRuleAgent_OnRoutedMessage(RoutedMessageEventSource source, QueuedMessageEventArgs e)
        {
            try
            {
                if (eventLogLevel >= 5)
                    EventLog.WriteEntry("RoutingRuleAgent", "Entering OnRouted for message " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 2);

                // look to see if there is an X-RoutingRuleDomain header
                Microsoft.Exchange.Data.Mime.Header domainheader;
                domainheader = e.MailItem.Message.MimeDocument.RootPart.Headers.FindFirst("X-MS-Exchange-Organization-RoutingRuleDomain");

                if (domainheader != null)
                {
                    // if header exists, check to see if it is valid
                    Microsoft.Exchange.Data.Transport.RoutingDomain overridedomain;
                    overridedomain = new RoutingDomain((domainheader.Value.ToString()));

                    if (overridedomain.IsValid())
                    {
                        // only resubmit if we haven't already
                        // check X-RoutingRuleCompleted
                        Microsoft.Exchange.Data.Mime.Header overrideheader;
                        overrideheader = e.MailItem.Message.MimeDocument.RootPart.Headers.FindFirst("X-MS-Exchange-Organization-RoutingRuleCompleted");

                        if (overrideheader == null)
                        {
                            if (eventLogLevel >= 3)
                                EventLog.WriteEntry("RoutingRuleAgent", "DEFER message " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 3);

                            // this is a bad way to resubmit the message because
                            // DEFER will show in the tracking log
                            // TODO: come up with a better method

                            source.Defer(System.TimeSpan.FromMilliseconds(5));
                        }
                    }
                }

                if (eventLogLevel >= 5)
                    EventLog.WriteEntry("RoutingRuleAgent", "Exiting OnRouted for message " + e.MailItem.Message.MessageId.ToString() + ".", EventLogEntryType.Information, 9);
            }
            catch (Exception ex)
            {
                if (eventLogLevel >= 0)
                    EventLog.WriteEntry("RoutingRuleAgent", "Exception (OnRouted): " + ex.Message + "\n" + ex.StackTrace, EventLogEntryType.Error, 10);
            }
            finally
            {
                // TODO: Decide what to do with the message if we fail.  Since this could be used in compliance scenarios, perhaps we should poison it or suspend it or something?
            }
        }
    }
}

